package com.libui.webview

import android.annotation.SuppressLint
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.os.Message
import android.util.AttributeSet
import android.util.Base64
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.webkit.WebSettings.LOAD_DEFAULT
import com.libbase.Weak
import com.libbase.Wrapper
import com.libbase.extension.downloadLocation
import com.libbase.extension.encodeURIComponent
import com.libbase.extension.nameExtension
import com.libbase.utils.FileUtils
import com.libbase.utils.ThreadUtils
import com.libnetwork.httpservice.HttpService
import com.libui.LibUI
import com.libui.R
import com.libui.dialog.DialogEx
import com.libui.dialog.DialogType
import com.libui.navigatonbar.FontData
import com.libui.navigatonbar.MenuData
import com.libui.navigatonbar.StyleData
import com.libui.saveToAlbum
import com.libui.toast.Toast
import com.libui.utils.FileChooserUtils
import com.libui.webview.jsinterface.JSInterface
import com.tencent.smtt.export.external.extension.interfaces.IX5WebViewClientExtension
import com.tencent.smtt.export.external.extension.proxy.ProxyWebViewClientExtension
import com.tencent.smtt.export.external.interfaces.WebResourceError
import com.tencent.smtt.export.external.interfaces.WebResourceRequest
import com.tencent.smtt.export.external.interfaces.WebResourceResponse
import com.tencent.smtt.sdk.*
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.addTo
import io.reactivex.rxjava3.subjects.PublishSubject
import java.io.IOException
import java.net.URL
import java.util.*
import kotlin.math.abs


@SuppressLint("SetJavaScriptEnabled")
open class WebViewEx : WebView {
    internal class CallbackClient(val webView: WebView) : WebViewCallbackClient {
        private var isYScrolling: Boolean = false

        override fun invalidate() {}

        override fun onTouchEvent(event: MotionEvent, view: View): Boolean {
            if (event.action == MotionEvent.ACTION_DOWN) {
                isYScrolling = false
                webView.requestDisallowInterceptTouchEvent(true)
            } else if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {
                isYScrolling = false
                webView.requestDisallowInterceptTouchEvent(false)
            }

            return webView.super_onTouchEvent(event)
        }

        override fun overScrollBy(
            deltaX: Int,
            deltaY: Int,
            scrollX: Int,
            scrollY: Int,
            scrollRangeX: Int,
            scrollRangeY: Int,
            maxOverScrollX: Int,
            maxOverScrollY: Int,
            isTouchEvent: Boolean,
            view: View
        ): Boolean {
            if (!isYScrolling) {
                isYScrolling = abs(deltaY) > abs(deltaX)
            }

            return webView.super_overScrollBy(
                deltaX,
                deltaY,
                scrollX,
                scrollY,
                scrollRangeX,
                scrollRangeY,
                maxOverScrollX,
                maxOverScrollY,
                isTouchEvent
            )
        }

        override fun computeScroll(view: View) {
            webView.super_computeScroll()
        }

        override fun onOverScrolled(
            scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean, view: View
        ) {
            if ((clampedX && !isYScrolling) || clampedY) {
                webView.requestDisallowInterceptTouchEvent(false)
            }

            webView.super_onOverScrolled(scrollX, scrollY, clampedX, clampedY)
        }

        override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int, view: View) {
            webView.super_onScrollChanged(l, t, oldl, oldt)
        }

        override fun dispatchTouchEvent(ev: MotionEvent, view: View): Boolean {
            return webView.super_dispatchTouchEvent(ev)
        }

        override fun onInterceptTouchEvent(ev: MotionEvent, view: View): Boolean {
            return webView.super_onInterceptTouchEvent(ev)
        }
    }

    companion object {
        const val TAG: String = "WebViewEx"
        val testHtmlUrl: String?
            get() {
                val htmlPath = LibUI.instance.dataSource?.htmlPath ?: return null

                return "file:///android_asset/${htmlPath}/test.html"
            }
    }

    private val compositeDisposable = CompositeDisposable()

    private lateinit var callbackClient: CallbackClient

    var delegate: WebViewDelegate? by Weak()

    val titleSubject: PublishSubject<Wrapper<String>> = PublishSubject.create()

    @Suppress("DEPRECATION")
    private val tbsReaderViewDelegate = lazy {
        val view = TbsReaderView(
            context
        ) { _, _, _ -> }
        view.layoutParams = LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT
        )
        addView(view)

        return@lazy view
    }

    @Suppress("DEPRECATION")
    private val tbsReaderView: TbsReaderView by tbsReaderViewDelegate

    private val mWebViewClientExtension: IX5WebViewClientExtension =
        object : ProxyWebViewClientExtension() {
            override fun invalidate() {}

            override fun onReceivedViewSource(data: String) {}

            override fun onTouchEvent(event: MotionEvent, view: View): Boolean {
                return callbackClient.onTouchEvent(event, view)
            }

            override fun onInterceptTouchEvent(ev: MotionEvent, view: View): Boolean {
                return callbackClient.onInterceptTouchEvent(ev, view)
            }

            override fun dispatchTouchEvent(ev: MotionEvent, view: View): Boolean {
                return callbackClient.dispatchTouchEvent(ev, view)
            }

            override fun overScrollBy(
                deltaX: Int,
                deltaY: Int,
                scrollX: Int,
                scrollY: Int,
                scrollRangeX: Int,
                scrollRangeY: Int,
                maxOverScrollX: Int,
                maxOverScrollY: Int,
                isTouchEvent: Boolean,
                view: View
            ): Boolean {
                return callbackClient.overScrollBy(
                    deltaX,
                    deltaY,
                    scrollX,
                    scrollY,
                    scrollRangeX,
                    scrollRangeY,
                    maxOverScrollX,
                    maxOverScrollY,
                    isTouchEvent,
                    view
                )
            }

            override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int, view: View) {
                callbackClient.onScrollChanged(l, t, oldl, oldt, view)
            }

            override fun onOverScrolled(
                scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean, view: View
            ) {
                callbackClient.onOverScrolled(scrollX, scrollY, clampedX, clampedY, view)
            }

            override fun computeScroll(view: View) {
                callbackClient.computeScroll(view)
            }
        }

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context, attrs, defStyleAttr
    ) {
        initWebView()
    }

    init {
        @Suppress("DEPRECATION")
        settings.javaScriptEnabled = true

        settings.allowFileAccess = true
        settings.allowContentAccess = true
        settings.setSupportMultipleWindows(true)

        //支持webView缩放
        settings.setSupportZoom(true)
        settings.useWideViewPort = true
        settings.loadWithOverviewMode = true
        settings.builtInZoomControls = true
        settings.displayZoomControls = false
        settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN

        //webView缓存开启
        settings.cacheMode = LOAD_DEFAULT
        settings.domStorageEnabled = true
        val path: String =
            FileUtils.instance.getCacheDir(ContextWrapper(context)).toString() + "/webCache"
        settings.setAppCachePath(path)
        settings.setAppCacheEnabled(true)
        settings.databaseEnabled = true

        setWebContentsDebuggingEnabled(true)
    }

    open fun showLoading() {
    }

    open fun hideLoading() {
    }

    private fun showLoadErrorHtml(url: String) {
        val htmlPath = LibUI.instance.dataSource?.htmlPath ?: return

        val htmlUrl =
            Uri.parse("file:///android_asset/${htmlPath}/load-error.html").buildUpon().fragment(url)
                .build().toString()

        loadUrl(htmlUrl)
    }

    private fun loadFile(filePath: String) {
        if (tbsReaderView.preOpen(filePath.nameExtension, false)) {
            val bundle = Bundle()
            bundle.putString("filePath", filePath)
            bundle.putString(
                "tempPath", FileUtils.instance.getDocumentsDir(
                    ContextWrapper(context)
                ) ?: ""
            )
            tbsReaderView.openFile(bundle)

            addShareMenu("file", filePath)
        } else {
            Log.e(TAG, "$filePath is not support")
            showLoadErrorHtml(filePath)
        }
    }

    private fun addShareMenu(url: String) {
        if (url.contains("zlibHtml.html")) {
            addShareMenu("url", url)
        } else {
            ThreadUtils.instance.execute {
                try {
                    val conn = URL(url).openConnection().apply { connect() }

                    val key = conn.headerFields.keys.firstOrNull { key: String? ->
                        key?.uppercase()?.contains("Attachment".uppercase()) ?: false
                    }
                    if (key != null) {
                        ThreadUtils.instance.executeInMainThread {
                            addShareMenu("url", url)
                        }
                    }
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
    }

    private fun addShareMenu(shareType: String, url: String) {
        val shareUrl =
            "app://handler/system/share/system?type=${shareType}&url=${url.encodeURIComponent()}"

        delegate?.setRightMenus(
            this, listOf(
                MenuData(
                    title = "icon,dotMore",
                    children = listOf(
                        MenuData(
                            title = "其他应用打开",
                            url = shareUrl,
                        )
                    ),
                    style = StyleData(null, FontData(null, 20F, null)),
                )
            )
        )
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()

        if (tbsReaderViewDelegate.isInitialized()) {
            tbsReaderView.onStop()
        }
    }

    private fun initWebView() {
        callbackClient = CallbackClient(this)

        if (x5WebViewExtension != null) {
            x5WebViewExtension.webViewClientExtension = mWebViewClientExtension
        }

        setWebViewCallbackClient(callbackClient)

        addJavascriptInterface(JSInterface(this), JSInterface.key)

        webChromeClient = object : WebChromeClient() {
            override fun onCreateWindow(
                webView: WebView, isDialog: Boolean, isUserGesture: Boolean, resultMsg: Message
            ): Boolean {
                return if (isDialog) {
                    DialogEx.CustomDialogBuilder(UUID.randomUUID().toString())
                        .setResultMsg(resultMsg).build(context)?.show()
                    true
                } else {
                    delegate?.onCreateWindow(webView, false, isUserGesture, resultMsg) ?: push(
                        resultMsg
                    )
                }
            }

            override fun onCloseWindow(webView: WebView) {
                if (webView.id != R.id.web_view) {
                    delegate?.onCloseWindow(webView)
                    return
                }

                val dialogId = webView.tag as? String ?: return

                DialogEx.closeDialog(dialogId, "")
            }

            override fun onReceivedTitle(webView: WebView?, title: String?) {
                super.onReceivedTitle(webView, title)

                // title为链接地址时不显示
                val host: String? = webView?.url?.let { Uri.parse(it).host }
                if (!host.isNullOrEmpty() && title?.contains(host) == true) {
                    return
                }

                titleSubject.onNext(Wrapper(title))
            }

            override fun onShowFileChooser(
                webView: WebView,
                filePathCallback: ValueCallback<Array<Uri>>,
                fileChooserParams: FileChooserParams
            ): Boolean {
                FileChooserUtils.instance.chooseFile(context, filePathCallback, fileChooserParams)
                return true
            }
        }

        webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(webView: WebView, url: String): Boolean {
                return delegate?.shouldOverrideUrlLoading(webView, url) ?: false
            }

            override fun onPageStarted(webView: WebView, url: String?, favicon: Bitmap?) {
                super.onPageStarted(webView, url, favicon)

                delegate?.onPageStarted(webView, url, favicon)

                url?.let { addShareMenu(it) }
            }

            override fun onPageFinished(webView: WebView, url: String?) {
                super.onPageFinished(webView, url)

                delegate?.onPageFinished(webView, url)
            }

            override fun onReceivedError(
                webView: WebView,
                webResourceRequest: WebResourceRequest?,
                webResourceError: WebResourceError?
            ) {
                super.onReceivedError(webView, webResourceRequest, webResourceError)

                delegate?.onReceivedError(webView, webResourceRequest, webResourceError)
            }

            override fun onReceivedHttpError(
                webView: WebView,
                webResourceRequest: WebResourceRequest?,
                webResourceResponse: WebResourceResponse?
            ) {
                super.onReceivedHttpError(webView, webResourceRequest, webResourceResponse)

                delegate?.onReceivedHttpError(webView, webResourceRequest, webResourceResponse)
            }

            override fun onLoadResource(webViw: WebView, url: String?) {
                super.onLoadResource(webViw, url)
            }
        }

        setDownloadListener {
                url: String,
                _: String, // userAgent
                contentDisposition: String, // contentDisposition
                mimeType: String, // mimetype
                _: Long, // contentLength
            ->
            Log.i(TAG, "start download")
            titleSubject.onNext(Wrapper("详情"))

            val (isBase64DataImage, base64ImageData) = isBase64ImageData(url)
            if (isBase64DataImage) {
                val bitmapByteArray = Base64.decode(base64ImageData, Base64.DEFAULT)
                val bitmap = BitmapFactory.decodeByteArray(bitmapByteArray, 0, bitmapByteArray.size)

                bitmap.saveToAlbum(context, "ql-qrcode.png")

                Toast
                    .ToastBuilder("保存成功")
                    .setDuration(800)
                    .build(context)
                    .show()
            } else {
                val fileName = URLUtil.guessFileName(url, contentDisposition, mimeType)
                url.downloadLocation(context, fileName)?.let { filePath ->
                    if (FileUtils.instance.fileIsExists(filePath)) {
                        loadFile(filePath)
                        return@setDownloadListener
                    }

                    showLoading()

                    HttpService.instance.download(url, filePath)
                        .observeOn(AndroidSchedulers.mainThread()).subscribe({ progress ->
                            progress.filePath?.let {
                                hideLoading()

                                loadFile(it)
                            }
                        }, { error ->
                            hideLoading()

                            DialogEx.DialogBuilder(
                                DialogType.ALERT,
                                UUID.randomUUID().toString(),
                                error.localizedMessage ?: "下载失败！"
                            ).build(context)?.show()
                        }).addTo(compositeDisposable)
                } ?: run {
                    Toast
                        .ToastBuilder("下载文件失败!")
                        .setDuration(800)
                        .build(context)
                        .show()
                }
            }
        }
    }

    private fun isBase64ImageData(url: String): Pair<Boolean, String?> {
        val values = url.split(",")
        return Pair(values.getOrNull(0)?.contains("base64") == true, values.getOrNull(1))
    }

    private fun push(resultMsg: Message): Boolean {
        val moduleId = UUID.randomUUID().toString()
        val pageId = UUID.randomUUID().toString()

        return LibUI.instance.adapter?.push(
            "app://page/componentBrowser?pageId=webBrowser", mapOf(
                "moduleId" to moduleId,
                "pageId" to pageId,
                "value" to mapOf(
                    "type" to "webView",
                    "value" to resultMsg,
                ),
            )
        ) ?: false
    }

    fun evaluateJavascriptInMainThread(var1: String, var2: ValueCallback<String>) {
        ThreadUtils.instance.executeInMainThread {
            evaluateJavascript(var1, var2)
        }
    }
}